
#include "tunnelmgr.h"
#include "coid/comm/binstream/binstreambuf.h"
#include "coid/comm/binstream/inoutstream.h"
#include "coid/comm/binstream/httpstreamtunnel.h"
#include "coid/comm/rnd.h"

#include "coid/server/treemgr.h"
#include "coid/server/serviceinst.h"
#include "coid/server/acceptor.h"
#include "coid/server/server.h"

#include "coid/comm/metastream/fmtstreamxml2.h"
#include "fmtsoapxml.h"
#include "soap_msg.h"

static void operation_status( opcd e, metastream& meta )
{
    if(e) {
        meta.stream_reset(1,false);
        meta.xstream_out(e,"err");
    }
    meta.stream_flush();
}

static void operation_status( opcd e, binstream& bin )
{
    if(e) {
        bin.reset_write();
        bin << e;
    }
    bin.flush();
}

////////////////////////////////////////////////////////////////////////////////
uint64 TunnelMgr::record::generate_ssid()
{
    rnd_int ri( (uint)time(0) );
    uint64 r = ri.rand0();
    _ssid = (r << 32) + (r ^ (uints)this);
    return _ssid;
}

////////////////////////////////////////////////////////////////////////////////
opcd TunnelMgr::process_remote_call(
	dynarray<uchar>& buf, 
	const charstr& qry, 
	netAddress& addr, 
	net_inout uint64& ssid, 
	net_out charstr& content_type )
{
	binstreambuf bbuf(buf);

	opcd err = process_call(bbuf, qry, addr, ssid, content_type, false);

	bbuf.swap(buf);

	return err;
}

////////////////////////////////////////////////////////////////////////////////
opcd TunnelMgr::process_local_call( httpstreamcoid& net, token qry, netAddress& addr )
{
	charstr ct;
	uint64 ssid = net.get_session_id();

	return process_call(net, qry, addr, ssid, ct, true);
}

////////////////////////////////////////////////////////////////////////////////
//opcd TunnelMgr::process_call( httpstreamcoid& net, token qry, netAddress& addr )
opcd TunnelMgr::process_call( binstream& net, token qry, netAddress& addr, uint64& ssid, charstr& content_type, bool local_call )
{
    //--------coid call------------
    //is recognized by the path starting with ?o or ?c
    // ?o is used to open connection to a remote object
    // ?c is used to perform calls on already established connection
    //
    // letters following after the ?c or ?o specify the input and output format: <in>.<out>=<id>
    //
    // x        fmtstreamxml, canbe followed by further characters to specify xml format
    //           e.g xr for xml-rpc standard, xs for soap, etc.
    // j        fmtstreamjson, json standard
    // c        fmtstreamcxx
    // t        fmtstreamcxx without metadata, with newline separators between arguments
    // a        comma separated arguments within URL, input only
    //
    //  example:
    //   ?ca.c=12345    simple input arguments within URL, response in cxxfmt metadata
    //   ?oa.x=talk     open connection to 'talk' service

    enum { QRY_UNKNOWN=0, QRY_OPEN, QRY_CALL, QRY_SERVICES, QRY_METHODS, QRY_TYPES, };
    int qtype = QRY_UNKNOWN;

    char q = qry.first_char();
    if( q == 'o' )      qtype = QRY_OPEN;
    else if( q == 'c' ) qtype = QRY_CALL;
    else if( q == 's' ) qtype = QRY_SERVICES;
    else if( q == 'm' ) qtype = QRY_METHODS;
    else if( q == 't' ) qtype = QRY_TYPES;

    if( qtype == QRY_UNKNOWN )
        return ersUNKNOWN "unrecognized query type";

    ++qry;  //skip 'o' or 'c'

    //pry input and output format string
    token fmtin = qry.cut_left('.');
    token fmtout = qry.cut_left('=');

    //following is either a service.version string or a method name, for ?o and ?c respectively
    token smid = qry.cut_left('&');
    uint32 oid = UMAX32;

    if( qtype == QRY_OPEN ) {
        token oidt = smid.cut_right_back('.');
        oid = oidt.is_empty() ? UMAX32 : oidt.touint();
    }

    //get the type name for a type query
    token typ = token::empty();
    if( qtype == QRY_TYPES )
        typ = qry.cut_left('&');

    fmtstream* binp=0;
    fmtstream* boutp=0;

    opcd e=0;
    bool bmetaread=true, bmetawrite=true;

    binstreamconstbuf bcb;

    //input stream binding
    if( fmtin.first_char() == 'a' )                 //comma separated arguments within URL
    {
        _fmtcxxi.set_separators( "", "", "&", "&" );
        bmetaread = false;
        
        bcb.set(qry);
        _fmtcxxi.bind( bcb, binstream::BIND_INPUT );
        binp = &_fmtcxxi;
    }
    else if( fmtin.first_char() == 'c' )            //metastream with cxx formatting
    {
        _fmtcxxi.set_default_separators(true);

        _fmtcxxi.bind( net, binstream::BIND_INPUT );
        binp = &_fmtcxxi;
    }
    else if( fmtin.first_char() == 't' )            //metastream with cxx formatting
    {
        _fmtcxxi.set_separators( "", "", ",", "," );
        bmetaread = false;

        _fmtcxxi.bind( net, binstream::BIND_INPUT );
        binp = &_fmtcxxi;
    }
    else if( fmtin.first_char() == 'j' )            //metastream with json formatting
    {
        _fmtjson.bind( net, binstream::BIND_INPUT );
        binp = &_fmtjson;
    }
    else if( fmtin.first_char() == 'x' )            //metastream with xml formatting
    {
        _fmtxml.bind( net, binstream::BIND_INPUT );
        binp = &_fmtxml;
    }
    else
        return ersINVALID_PARAMS "unsupported input format";


    //output stream binding
    if( fmtout.first_char() == 'c' )                //metastream with cxx formatting
    {
        _fmtcxxo.set_default_separators(true);

        //net.set_content_type( "text/plain" );
        content_type = "text/plain";
		_fmtcxxo.bind( net, binstream::BIND_OUTPUT );
        boutp = &_fmtcxxo;
    }
    else if( fmtout.first_char() == 't' )                //metastream with cxx formatting
    {
        _fmtcxxo.set_separators( "", "", ",\n", "," );
        bmetawrite = false;

        //net.set_content_type( "text/plain" );
		content_type = "text/plain";
        _fmtcxxo.bind( net, binstream::BIND_OUTPUT );
        boutp = &_fmtcxxo;
    }
    else if( fmtout.first_char() == 'j' )            //metastream with json formatting
    {
        //net.set_content_type( "text/plain" );
		content_type = "text/json";
        _fmtjson.bind( net, binstream::BIND_OUTPUT );
        boutp = &_fmtjson;
    }
    else if( fmtout.first_char() == 'x' )            //metastream with xml formatting
    {
        //net.set_content_type( "text/xml" );
		content_type = "text/xml";
        _fmtxml.bind( net, binstream::BIND_OUTPUT );
        boutp = &_fmtxml;
    }
    else
        return ersINVALID_PARAMS "unsupported output format";

    if(local_call)
        static_cast<httpstreamcoid&>(net).set_content_type(content_type);

    _meta.bind_formatting_streams( *binp, *boutp );
    _meta.enable_meta_read( bmetaread );
    _meta.enable_meta_write( bmetawrite );

    binstream& bin = _meta.get_binstream();
    //uint64 ssid = net.get_session_id();

    //if( ssid == 0 )
    //    ssid = oidt.touint64();

    if( qtype == QRY_OPEN )
    {
        DASSERT( ssid == 0 );
        ssid = create_slot();

        if(local_call)
            static_cast<httpstreamcoid&>(net).set_session_id(ssid);

        record* p = record::get_from_ssid(ssid);

        NODEREF<CoidAcceptorCoid> coidacc;
        uint svciid = UMAX32;

        e = SRVROOTA._objmgr.get_node( SRVROOTA._accpcoid_nodeid, coidacc );
        if(!e)
            e = coidacc->attach_service_remote( oid, smid, bin, &_meta, &svciid );

        if(e)
            get_schm().free(p);
        else {
            p->_soid = svciid;
            _cmdin->post_info(1) << "tunneler created instance of service: " << smid
                << " (ssid: " << p->_ssid << EOM;
        }
        operation_status(e,_meta);
    }
    else if( qtype == QRY_SERVICES )
    {
        bin >> BINSTREAM_ACK;
        e = qry_services( smid, _meta );
        operation_status(e,_meta);
    }
    else if( qtype == QRY_METHODS )
    {
        bin >> BINSTREAM_ACK;
        e = qry_methods( smid, _meta );
        operation_status(e,_meta);
    }
    else if( qtype == QRY_TYPES )
    {
        bin >> BINSTREAM_ACK;
        e = qry_types( smid, typ, _meta );
        operation_status(e,_meta);
    }
    else
    {
        record* p;
        e = verify_ssid( ssid, &p );
        if(e) {
            _cmdin->post_error(1) << "invalid session id: " << ssid << " error: " << opcd_formatter(e) << EOM;
            operation_status(e,_meta);
            return e;
        }

        try {
            char k = smid.first_char();
            if( k>='0' && k<='9' )
                e = execute( p, smid.touint(), bin, addr, &_meta );
            else
                e = execute( p, smid, bin, addr, &_meta );
        }
        catch( opcd exc ) { e = exc; }
        operation_status(e,_meta);
    }

    return e;
}
////////////////////////////////////////////////////////////////////////////////

opcd TunnelMgr::process_remote_soap_call(
	dynarray<uchar>& buf, 
	charstr& service,
	netAddress& addr, 
	uint64& ssid )
{
	binstreambuf bs(buf);
	fmtsoapxml fs(bs);
	
	try {
		record rec; rec._ssid = 123; rec._soid = 0;

		_meta.bind_formatting_streams( fs, fs );
		_meta.enable_meta_read( true );
		_meta.enable_meta_write( true );

		NODEREF<CoidAcceptorCoid> coidacc;
		opcd e = SRVROOTA._objmgr.get_node( SRVROOTA._accpcoid_nodeid, coidacc );
		if(e) throw soap_exception(e.error_desc());

		fs.parse_soap_header();

		uint svciid = 0;

		e = coidacc->attach_service_remote( UMAX32, service, nullstream, 0, &svciid );
		if(e) throw soap_exception(e.error_desc());

		rec._soid = svciid;

		e = execute( &rec, fs.method_name(), bs, addr, &_meta );
		if (e) throw soap_exception(e.error_desc());
	}
	catch(const lexer::lexception & e) {
		fs.createErrorMessage(e.text());
	}
	catch(const soap_exception & e) {
		fs.createErrorMessage(e.error_desc());
	}
	catch(const opcd & e) {
		fs.createErrorMessage(e.error_desc());
	}

	bs.swap(buf);
	charstr str;

	str.takeover(*reinterpret_cast<dynarray<char,uint>*>(&buf));

	_cmdin->post_info(1) << "SOAP response: " << str << EOM;

	str.handover(*reinterpret_cast<dynarray<char,uint>*>(&buf));

	buf.resize(buf.size()-1);

	return 0;
}


////////////////////////////////////////////////////////////////////////////////
opcd TunnelMgr::process_local_soap_call(
	httpstreamcoid& net, 
	charstr& service,
	netAddress& addr, 
	uint64& ssid )
{
	fmtsoapxml fs(net);

	try {
		record rec; rec._ssid = 123; rec._soid = 0;

		_meta.bind_formatting_streams( fs, fs );
		_meta.enable_meta_read( true );
		_meta.enable_meta_write( true );
		
		NODEREF<CoidAcceptorCoid> coidacc;
		opcd e = SRVROOTA._objmgr.get_node( SRVROOTA._accpcoid_nodeid, coidacc );
		if(e) throw soap_exception(e.error_desc());

		fs.parse_soap_header();

		/*uint64 ssid = fs.get_session_id();

		if( ssid == 0 )
		{
			ssid = create_slot();
			ths.set_session_id( ssid );
		}*/

		NODEREF<ServiceInstanceCoid> psvci;

		e = coidacc->attach_service_inst( UMAX32, service, nullstream, 0, psvci );
		if(e) throw soap_exception(e.error_desc());

		int method_id = psvci->get_method_id(fs.method_name());
		if (method_id == -1)
			throw soap_exception("unknown method name");

		static int u=0;

		/*if( u==2 ) {
			binstreambuf o;
	
			fs.begin_soap_envelope(&o);

			e=psvci->dispatch(method_id,o,&_meta);
			if( e ) throw soap_exception(e.error_desc());

			fs.end_soap_envelope();
		} 
		else */{
			fs.begin_soap_envelope();

			e=psvci->dispatch(method_id,net,&_meta);
			if( e ) throw soap_exception(e.error_desc());

			fs.end_soap_envelope();
		}

		++u;
		net.flush();
	}
	catch(const lexer::lexception & e) {
		fs.createErrorMessage(e.text());
		net.flush();
	}
	catch(const soap_exception & e) {
		fs.createErrorMessage(e.error_desc());
		net.flush();
	}
	catch(const opcd & e) {
		fs.createErrorMessage(e.error_desc());
		net.flush();
	}

	/*bs.swap( (dynarray<uchar>&)buf );
	charstr str;

	str.takeover(*reinterpret_cast<dynarray<char>*>(&buf));

	_cmdin->post_info(1) << "SOAP response: " << str << EOM;

	str.handover(*reinterpret_cast<dynarray<char>*>(&buf));

	buf.resize(buf.size()-1);*/

	return 0;
}

////////////////////////////////////////////////////////////////////////////////
opcd TunnelMgr::process_generic_open( httpstreamcoid::header& hdr, cachestream& net, netAddress& addr )
{
    return ersNOT_IMPLEMENTED;
}

////////////////////////////////////////////////////////////////////////////////
opcd TunnelMgr::process_tunnel( httpstreamcoid::header& hdr, cachestream& net, netAddress& addr )
{
    httpstreamtunnel ths( hdr, net );
    ths.set_response( "200 OK" );
    ths.set_connection_type( hdr._bclose );

    uint64 ssid = ths.get_session_id();

    if( ssid == 0 )
    {
        ssid = create_slot();
        ths.set_session_id( ssid );

        record* p = record::get_from_ssid(ssid);

        NODEREF<CoidAcceptorCoid> coidacc;
        uint svciid = UMAX32;

        opcd e = SRVROOTA._objmgr.get_node( SRVROOTA._accpcoid_nodeid, coidacc );
        if(!e) {
            e = coidacc->attach_service_remote( ths, addr, &svciid );
            if(!e)
                p->_soid = svciid;
        }

        if(e)
            get_schm().free(p);
        operation_status(e,_meta);

        return e;
    }

    opcd e;
    try {
        uint mid;
        ths >> mid;

        e = execute( ssid, mid, ths, addr, 0 );
    }
    catch(opcd exc) { e = exc; }
    operation_status(e,ths);
    return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd TunnelMgr::verify_ssid( uint64 ssid, record** pp )
{
    record* p = record::get_from_ssid(ssid);
    opcd e=0;

    if( !p || !get_schm().check(p) ||  p->_ssid != ssid )
        e = ersINVALID_SESSION "ssid";
    else
        *pp = p;

    return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd TunnelMgr::execute( uint64 ssid, uint mid, binstream& bin, netAddress& addr, metastream* meta )
{
    record* p;
    opcd e = verify_ssid( ssid, &p );
    if(e)  return e;

    return execute( p, mid, bin, addr, meta );
}

////////////////////////////////////////////////////////////////////////////////
opcd TunnelMgr::execute( record* p, uint mid, binstream& bin, netAddress& addr, metastream* meta )
{
    NODEREF<ServiceInstanceCoid> svci;

    opcd e = SRVROOTA._objmgr.get_node( p->_soid, svci );
    if(!e)
    {
        //TODO: add meta
        e = svci->dispatch_call( bin, mid, meta );
    }
    else
    {
        bin.acknowledge(true);
        e = ersFE_NO_SUCH_SERVICE;
    }

    return e;
}

////////////////////////////////////////////////////////////////////////////////
opcd TunnelMgr::execute( record* p, token meth, binstream& bin, netAddress& addr, metastream* meta )
{
    NODEREF<ServiceInstanceCoid> svci;

    opcd e = SRVROOTA._objmgr.get_node( p->_soid, svci );
    if(!e)
    {
        uint mid = svci->get_method_id(meth);
        if( mid == UMAX32 ) {
            bin.acknowledge(true);
            _cmdin->post_error(1) << "invalid method: " << meth << ", of service: " << svci->name()
                << " (ssid: " << p->_ssid << EOM;
            e = ersFE_UNKNOWN_METHOD;
        }
        else
            e = svci->dispatch_call( bin, mid, meta );
    }
    else
    {
        bin.acknowledge(true);
        e = ersFE_NO_SUCH_SERVICE;
    }

    return e;
}
